//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#ifndef __ELASTOS_I8042_H__
#define __ELASTOS_I8042_H__

//
// I/O port accesses, and definitions for the i8042
//

#define i8042_READ_STATUS_PORT(_p_)     Inb((ioport_t)((ioport_t)(_p_) + 4))
#define i8042_WRITE_CMD_PORT(_p_, _c_)  \
                                Outb((ioport_t)((ioport_t)(_p_) + 4), (_c_))
#define i8042_READ_DATA_PORT(_p_)       Inb((ioport_t)(_p_))
#define i8042_WRITE_DATA_PORT(_p_, _c_) Outb((ioport_t)(_p_),(_c_))

#define KBD_INIT_TIMEOUT    1000    // Timeout in ms for initializing the kbd
#define KBC_TIMEOUT         250     // Timeout in ms for sending to keyboard
                                    // controller

// Timeout in ms for keyboard command acknowledge, 1000ms
#define KBD_TIMEOUT         500000

//
// Status Register Bits
//

#define KBD_STAT_OBF        0x01    // Keyboard output buffer full
#define KBD_STAT_IBF        0x02    // Keyboard input buffer full
#define KBD_STAT_SELFTEST   0x04    // Self test successful
#define KBD_STAT_CMD        0x08    // Last write was a command write (0=data)
#define KBD_STAT_UNLOCKED   0x10    // Zero if keyboard locked
#define KBD_STAT_MOUSE_OBF  0x20    // Mouse output buffer full
#define KBD_STAT_GTO        0x40    // General receive/xmit timeout
#define KBD_STAT_PERR       0x80    // Parity error

#define AUX_STAT_OBF (KBD_STAT_OBF | KBD_STAT_MOUSE_OBF)

//
// Keyboard Controller Commands
//
#define KBD_CCMD_READ_MODE      0x20    // Read mode bits
#define KBD_CCMD_WRITE_MODE     0x60    // Write mode bits
#define KBD_CCMD_GET_VERSION    0xA1    // Get controller version
#define KBD_CCMD_MOUSE_DISABLE  0xA7    // Disable mouse interface
#define KBD_CCMD_MOUSE_ENABLE   0xA8    // Enable mouse interface
#define KBD_CCMD_TEST_MOUSE     0xA9    // Mouse interface test
#define KBD_CCMD_SELF_TEST      0xAA    // Controller self test
#define KBD_CCMD_KBD_TEST       0xAB    // Keyboard interface test
#define KBD_CCMD_KBD_DISABLE    0xAD    // Keyboard interface disable
#define KBD_CCMD_KBD_ENABLE     0xAE    // Keyboard interface enable
#define KBD_CCMD_KBD_READOUPUT  0xD0    // Read output port
#define KBD_CCMD_WRITE_AUX_OBUF 0xD3    // Write to output buffer as if
                                        // initiated by the auxiliary device
#define i8042_CCMD_WRITE_MOUSE  0xD4    // Write the following byte
                                          // to the mouse

//
// Keyboard Commands
//
#define KBD_CMD_SET_LEDS        0xED    // Set keyboard leds
#define KBD_CMD_CODE_SET        0xF0    // Set scan code
#define KBD_CMD_SET_RATE        0xF3    // Set typematic rate
#define KBD_CMD_ENABLE          0xF4    // Enable scanning
#define KBD_CMD_DISABLE         0xF5    // Disable scanning
#define KBD_CMD_RESET           0xFF    // Reset
#define KBD_CMD_ECHO            0xEE    // Echo

//
//Controller Mode Register Bits
//
#define KBD_MODE_KBD_INT        0x01    // Keyboard data generage IRQ1
#define KBD_MODE_MOUSE_INT      0x02    // Mouse data generate IRQ12
#define KBD_MODE_SYS            0x04    // The system flag
#define KBD_MODE_NO_KEYLOCK     0x08    // The keylock doesn't affect the
                                        // keyboard if set
#define KBD_MODE_DISABLE_KBD    0x10    // Disable keyboard interface
#define KBD_MODE_DISABLE_MOUSE  0x20    // Disable mouse interface
#define KBD_MODE_KCC            0x40    // Scan code conversion to PC format
#define KBD_MODE_RFU            0x80    // The system reserve

//
// Keyboard Replies
//
#define KBD_REPLY_POR           0xAA    // Power on reset
#define KBD_REPLY_ACK           0xFA    // Command ACK
#define KBD_REPLY_RESEND        0xFE    // Command NACK, send the cmd again

//
// The led control key hold
//
#define KBD_CAPSLOCK_HOLD       0x0400
#define KBD_NUMLOCK_HOLD        0x0200
#define KBD_SCROLLLOCK_HOLD     0x0100

//
// CTRL, ALT, SHIFT status
//
#define KBD_CTRL                0x0001
#define KBD_ALT                 0x0002
#define KBD_SHIFT               0x0004
#define KBD_LCTRL               0x0100
#define KBD_LALT                0x0200
#define KBD_LSHIFT              0x0400
#define KBD_RCTRL               0x1000
#define KBD_RALT                0x2000
#define KBD_RSHIFT              0x4000

//
// The follow's operatoin be improve in the multiprocesser entironment.
// [juncheng_lau]
//

INLINE int KbdHasData(ioport_t Port)
{
    return (i8042_READ_STATUS_PORT(Port) & KBD_STAT_OBF);
}

#define KBD_RECEIVE_INTERRUPT(_p_) \
    ((i8042_READ_STATUS_PORT(_p_) & (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF)) \
        == KBD_STAT_OBF)

#define MOUSE_RECEIVE_INTERRUPT(_p_) \
    ((i8042_READ_STATUS_PORT(_p_) & (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF)) \
    == (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF))

INLINE int KSendCommand(ioport_t Port, uint8_t uData)
{
    int i = 0;
    while (i8042_READ_STATUS_PORT(Port) & KBD_STAT_IBF) {
        if (i++== KBD_TIMEOUT) {
            kprintf("KSendCommand 0x%02x failed, The status = 0x%02x\n", \
                                        uData, i8042_READ_STATUS_PORT(Port));
            return 1;
        }
    }

    i8042_WRITE_CMD_PORT(Port, uData);

    return 0;
}

INLINE int KSendData(ioport_t Port, uint8_t uData)
{
    int i = 0;

    while (i8042_READ_STATUS_PORT(Port) & KBD_STAT_IBF) {
        if (i++== KBD_TIMEOUT) {
            kprintf("KSendData 0x%02x failed, The status = 0x%02x\n", \
                                        uData, i8042_READ_STATUS_PORT(Port));
            return 1;
        }
        DzDelay(10);
    }

    i8042_WRITE_DATA_PORT(Port, uData);

    return 0;
}

INLINE int KRecvData(ioport_t Port, uint8_t *puData)
{
    int i = 0;

    while ((i8042_READ_STATUS_PORT(Port) & KBD_STAT_OBF) == 0) {
        if (i++== KBD_TIMEOUT) {
            kprintf("KRecvData failed, The status = 0x%02x\n", \
                                        i8042_READ_STATUS_PORT(Port));
            return 1;
        }
        DzDelay(10);
    }

    *puData = i8042_READ_DATA_PORT(Port);

    return 0;
}

#define KRecv(_p_,_c_) \
{ \
      while ((i8042_READ_STATUS_PORT(_p_) & KBD_STAT_OBF) == 0); \
      _c_ = i8042_READ_DATA_PORT(_p_); \
}

#define MRecv(_p_,_c_) \
{  \
      while ( (i8042_READ_STATUS_PORT(_p_) & 0x21) != 0x21); \
      _c_ = i8042_READ_DATA_PORT(_p_); \
}

INLINE int MRecvData(ioport_t Port, uint8_t *puData)
{
    int i = 0;

    while ((i8042_READ_STATUS_PORT(Port) & (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF))
        != (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF)) {
        if (i++== KBD_TIMEOUT) {
            kprintf("MRecvData failed, The status = 0x%02x", \
                                        i8042_READ_STATUS_PORT(Port));
            return 1;
        }
    }

    *puData = i8042_READ_DATA_PORT(Port);

    return 0;
}

INLINE void MOUSE_ENABLE(ioport_t Port)
{
    uint8_t uChar;

    KSendCommand(Port, KBD_CCMD_READ_MODE);
    KRecvData(Port, &uChar);
    uChar &=  ~0x20; // clear disable mouse bit
    KSendCommand(Port, KBD_CCMD_WRITE_MODE);
    KSendData(Port, uChar);
}

INLINE void MOUSE_DISABLE(ioport_t Port)
{
    uint8_t uChar;

    KSendCommand(Port, KBD_CCMD_READ_MODE);
    KRecvData(Port,&uChar);
    uChar |=  0x20;     // set disable mouse bit
    uChar &= ~0x02;     // clear interrupt enable bit
    KSendCommand(Port, KBD_CCMD_WRITE_MODE);
    KSendData(Port, uChar);
}

INLINE void MOUSE_ENABLE_INTERRUPTS(ioport_t Port)
{
    uint8_t uChar;

    KSendCommand(Port, KBD_CCMD_READ_MODE);
    KRecvData(Port, &uChar);
    uChar &= ~0x20;     // clear disable mouse bit
    uChar |=  0x02;     // set interrupt enable bit
    KSendCommand(Port, KBD_CCMD_WRITE_MODE);
    KSendData(Port, uChar);
}

INLINE void MOUSE_DISABLE_INTERRUPTS(ioport_t Port)
{
    uint8_t uChar;

    KSendCommand(Port, KBD_CCMD_READ_MODE);
    KRecvData(Port, &uChar);
    uChar &=  ~0x02;    // clear mouse interrupt enable bit
    KSendCommand(Port, KBD_CCMD_WRITE_MODE);
    KSendData(Port, uChar);
}

INLINE void KbdEnableReceiveInterrupt(ioport_t Port)
{
    uint8_t uChar;

    KSendCommand(Port, KBD_CCMD_READ_MODE);
    KRecvData(Port, &uChar);
    uChar &= ~0x10;     // clear disable keyboard bit
    uChar |=  0x41;     // set interrupt enable bit, and set-1 xlate
    KSendCommand(Port, KBD_CCMD_WRITE_MODE);
    KSendData(Port, uChar);
}

inline void KbdDisableReceiveInterrupt(ioport_t Port)
{
    uint8_t uChar;

    KSendCommand(Port, KBD_CCMD_READ_MODE);
    KRecvData(Port, &uChar);
    uChar |= 0x10;      // set disable keyboard bit
    uChar &=  ~0x41;    // set interrupt enable bit, and set-1 xlate
    KSendCommand(Port, KBD_CCMD_WRITE_MODE);
    KSendData(Port, uChar);
}

// Send keyboard command and argument can use this func
INLINE int KbdSendData(ioport_t Port, uint8_t uCommand)
{
    uint8_t uRetVal = 0;

    do {
        KSendData(Port, uCommand);
        KRecvData(Port, &uRetVal);

        if (KBD_REPLY_ACK == uRetVal) {
            break;
        }
        if (KBD_REPLY_RESEND != uRetVal) {
            return 1;
        }
    } while (1);

    return 0;
}

INLINE int KbdSendComandAndArgument(ioport_t Port,
    uint8_t uCommand, uint8_t uArgument)
{
    if (KbdSendData(Port, uCommand)) {
        return 1;
    }

    if (KbdSendData(Port, uArgument)) {
        return 1;
    }

    return 0;
}

#endif
